package com.zhq.blog.manage.domain.quartz.utils;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Random;

import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.zhq.blog.core.domain.Context;
import com.zhq.blog.core.domain.game.Game;
import com.zhq.blog.core.domain.game.GamePeriod;
import com.zhq.blog.core.domain.game.GamePeriod.GamePeriodState;
import com.zhq.blog.core.domain.game.GamePeriod.LotteryMethod;
import com.zhq.blog.core.domain.game.support.AutoBet;
import com.zhq.blog.core.domain.game.support.AutoBetBase.AutoBetType;
import com.zhq.blog.core.domain.game.support.AutoBetDetail;
import com.zhq.blog.core.domain.game.support.Cathectic;
import com.zhq.blog.core.domain.game.support.CathecticModel;
import com.zhq.blog.core.domain.game.support.PermutationBet;
import com.zhq.blog.core.domain.game.support.AutoBetBase.RunningState;
import com.zhq.blog.core.domain.game.support.Cathectic.MunualOrAuto;
import com.zhq.blog.core.domain.game.support.CathecticModelDetail;
import com.zhq.blog.core.domain.game.support.Odds;
import com.zhq.blog.core.domain.game.support.PermutationBetDetail;
import com.zhq.blog.core.domain.game.support.WinnerRecord;
import com.zhq.blog.core.domain.other.DetailsOfFunds;
import com.zhq.blog.core.domain.other.InternalMessage;
import com.zhq.blog.core.domain.other.DetailsOfFunds.DetailType;
import com.zhq.blog.core.domain.user.User;

/**
 * @title:QuartzUtils.java
 * @package com.zhq.blog.manage.domain.quartz.utils
 * @author 张恒强
 * @date 2014年12月27日 下午1:59:14
 * @version V1.0
 */
@Component
public class QuartzUtils {

	public static final int TO_BE_LOTTERY_COUNT = 4;

	public static final int BASE_PERIOD_NUMBER = 10000000;

	/**
	 * 自动生成指定游戏的下一期投注 查找所有未开奖的期数(开奖时间大于当前时间+5秒)，总和是否等于4，若不是，则需自动生成(4-总和)期 找到的
	 * 从最后一个开始 根据其开奖时间+频率生成下一期开奖时间 若没找到，则根据当前时间+频率+5秒生成下一期开奖时间
	 * 
	 * 查找所有未开奖的期数(开奖时间小于等于当前时间+5秒)，找到的全部需要开奖
	 * 
	 * @param context
	 * @param gameId
	 * @return 包含两个 第一个是要投注的游戏期数，第二个是要开奖的游戏期数
	 */
	@Transactional
	public List<GamePeriod> generatorGamePeriod(Context context, long gameId) {
		Game game = context.getGameRepository().findOne(gameId);
		Date currentDate = getCurrentDate();
		List<GamePeriod> not_Lotterys = context.getGamePeriodRepository()
				.find_not_lottery(gameId);
		List<GamePeriod> results = new ArrayList<GamePeriod>();;
		/**
		 * 大于4 只取获取的倒数两期
		 * 大于1  等于或者小于4  生成 TO_BE_LOTTERY_COUNT-size()+1期 取获取的倒数两期
		 * 正好等于1  这一期用来开奖，生成4期   取生成的第一期来投注
		 * 小于等于0  生成四期  不开奖 只取生成的第一期投注
		 */
		if (not_Lotterys != null && not_Lotterys.size()>0) {
			
			if(not_Lotterys.size()>4){
				results.addAll(not_Lotterys.subList(not_Lotterys.size()-2, not_Lotterys.size()));
			}else if(not_Lotterys.size()<=4 && not_Lotterys.size()>0){
				int size = not_Lotterys.size();
				if(size>1){//大于等于两期
					results.addAll(not_Lotterys.subList(not_Lotterys.size()-2, not_Lotterys.size()));
				}else{//只有一期  只有一期用来开奖，需要下面生成的第一期来进行投注
					results.addAll(not_Lotterys);
				}
				currentDate = not_Lotterys.get(0).getLotteryTime();
				for(int i=0;i<TO_BE_LOTTERY_COUNT-size+1;i++){
					currentDate = nextLotteryTime(currentDate, game);
					if(i ==0 && size <=1){
						results.add(0,context.getGamePeriodRepository()
						.save(new GamePeriod(not_Lotterys.get(0).getPeriod()
								+ 1+i, gameId, currentDate, game
								.getBaseNumber())));
						
					}else{
						context.getGamePeriodRepository()
						.save(new GamePeriod(not_Lotterys.get(0).getPeriod()
								+ 1+i, gameId, currentDate, game
								.getBaseNumber()));

					}
				}
			}
		} else {
			Object max_period = context.getGamePeriodRepository().find_by_gameId(gameId);
			long base = (max_period == null ? BASE_PERIOD_NUMBER:Long.parseLong(max_period.toString()));
			for (int i = 0; i < TO_BE_LOTTERY_COUNT; i++) {
				currentDate = nextLotteryTime(currentDate, game);
				if (i == 0) {
					GamePeriod gPeriod = new GamePeriod(base + 1
							+ i, gameId, currentDate, game.getBaseNumber());
					context.getGamePeriodRepository().save(gPeriod);
					results.add(gPeriod);
				} else {
					context.getGamePeriodRepository().save(
							new GamePeriod(base + 1 + i, gameId,
									currentDate, game.getBaseNumber()));
				}
			}
		}
		return results;
	}

	/**
	 * 对某游戏的某一期开奖 并设置该游戏的这一期的开奖结果 开奖结果 由哪几个号生成 中奖人数 这一期的状态为已开奖
	 * @param context
	 * @param gameId
	 * @param period
	 * @return 
	 */
	@Transactional
	public GamePeriod lotterying(Context context, long gameId, long period) {

		List<Odds> odds = context.getOddsRepository().find_all_by_game(gameId);// 升序

		List<Object[]> cathecitcGroup = context.getCathecticRepository()
				.find_all_by_game_and_period_grou_by_betnumber_asc(period,
						gameId);

		int resultNumber = -1;
		GamePeriod gp = context.getGamePeriodRepository().find_by_gameId_and_period(gameId, period).get(0);
		if(gp.getLotteryMethod().equals(LotteryMethod.MUNURAL)){//已经手工指定了结果  但是前台不能提前显示
			resultNumber = gp.getResultNumber();
		}else{
			if (CollectionUtils.isNotEmpty(cathecitcGroup)) {
				for (Object[] obj_arr : cathecitcGroup) {
					for (Odds o : odds) {
						if (Integer.parseInt(obj_arr[0].toString()) == o
								.getNumber()) {
							obj_arr[1] = Double.parseDouble(obj_arr[1].toString())
									* o.getStandard();
							break;
						}
					}
				}
				// sort
				Collections.sort(cathecitcGroup, new Comparator<Object[]>() {
					public int compare(Object[] o1, Object[] o2) {
						return Double.parseDouble(o1[1].toString()) > Double
								.parseDouble(o2[1].toString()) ? 1 : -1;
					}
				});
				resultNumber = Integer
						.parseInt(cathecitcGroup.get(0)[0].toString());
			}
			
		}
		if(resultNumber == -1){
			gp.setWinnerNumbers(0);
			gp.setResultNumber(new Random().nextInt(odds.size()));
			gp.setLotteryMethod(LotteryMethod.AUTO_RANDOM);
		}else{
			gp.setWinnerNumbers(context.getCathecticRepository().count_by_game_and_period_and_betnumber(period, gameId, resultNumber));
			gp.setResultNumber(resultNumber);
			gp.setLotteryMethod(LotteryMethod.AUTO);
		}
		gp.setResultFromNumbers(generateResultFromNumber(context.getGameRepository().findOne(gameId), gp.getResultNumber()));
		gp.setState(GamePeriodState.LOTTERYED);
		
		context.getGamePeriodRepository().save(gp);

		return gp;
	}
	
	/**
	 * 发放奖励 同时修改这一期的所有投注的一些属性 是否中奖  是否开奖 中奖金币
	 * 同时产生资金明细  存储中奖记录
	 * @param context
	 * @param gameId
	 * @param period
	 * @param result
	 */
	@Transactional
	public void award(Context context, long gameId, long period, int result) {
		double _odds = getOdds(context, gameId, period, result);
		List<Cathectic> winner_cathectics = context.getCathecticRepository().find_all_by_game_and_period_and_betnumber(period, gameId, result);
		if(CollectionUtils.isNotEmpty(winner_cathectics)){
			Game game = context.getGameRepository().findOne(gameId);
			for(Cathectic c :winner_cathectics){
				c.setIsLottery(true);
				c.setIsWinning(true);
				c.setWinningMoney(Long.valueOf(new DecimalFormat("0").format(Double.parseDouble((c.getBetMoney()*_odds)+""))));
				context.getCathecticRepository().save(c);
				User user = context.getUserRepository().findOne(c.getUserId());
				//设置资金明细
				context.getDetailsOfFundsRepository().save(new DetailsOfFunds(
						user.getId(), DetailType.BONUS, user.getCoin(), 
						user.getCoin()+c.getWinningMoney(), c.getWinningMoney(),
						game.getName(), period, result));
				//给玩家返奖
				context.getUserRepository().update_user_coin_for_winning_money(c.getUserId(), c.getWinningMoney());
				Long sum = context.getCathecticRepository().sum_by_game_period_user(gameId, user.getId(), period);
				context.getWinnerRecordRepository().save(
						new WinnerRecord(user.getId(), user.getUserID()+"", gameId, period,
								sum,c.getWinningMoney(), c.getWinningMoney()-sum));
			}
		}
		//设置未中奖的投注
		context.getCathecticRepository().update_set_lose_by_game_and_period_and_betnumber(period, gameId, result);
	}
	
	/**
	 * 修改所有参与某游戏的某期的自动投注的当前投注模式
	 * 需判断是否是从头运行、是否已删除、是否已停止,如果是 则不修改
	 * 如果不是 需判断是否到达设置的金币上下限 如果是 则设置为停止 
	 * 如果不是 则根据他的当前投注模式中是否包含上期的中奖号码 来修改下一期的当前模式
	 * @param context
	 * @param gameId
	 * @param period
	 * @param result
	 */
	@Transactional
	public void update_currentCathecticModel_of_autoBet(Context context,
			long gameId, long period, int result) {
		List<Long> auto_bet_ids = context.getCathecticRepository().find_all_autoBetId_by_game_period(period, gameId, MunualOrAuto.AUTO,AutoBetType.AUTO_BET);
		Game game = context.getGameRepository().findOne(gameId);
		if(CollectionUtils.isNotEmpty(auto_bet_ids)){
			for(Long auto_bet_id :auto_bet_ids){
				AutoBet autobet = context.getAutoBetRepository().findOne(auto_bet_id);
				if(autobet.getState().equals(RunningState.RUNNING)&&!autobet.isDeleted()&&!autobet.getFromHead()){
					User user = context.getUserRepository().findOne(autobet.getUserId());
					if(user.getCoin()>=autobet.getSuperLimit() || user.getCoin()<=autobet.getLowerLimit() || (period-autobet.getStartBetPeriod())>=autobet.getExecutePeriod()){
						autobet.setState(RunningState.STOPED);
						context.getAutoBetRepository().save(autobet);
						context.getInternalMessageRepository().save(new InternalMessage(10000, user.getId(),
								"您在"+game.getName()+"游戏设置的名称为"+autobet.getName()+"的自动投注已自动停止,原因可能是下面三种:\n1、已到达设置的金币上限.\n2、已达到设置的金币下限.\n3、已运行完设定的运行期数.\n该邮件为系统自动发送,请勿回复!!"));
					}else{
						List<CathecticModelDetail> cmodeldetail = 
									context.getCathecticModelDetailRepository().find_by_betnumber_modelId(autobet.getCurrentModelId(), result);
						List<AutoBetDetail> currentautobetDetail = 
									context.getAutoBetDetailRepository().find_by_autobetId_modelId(auto_bet_id, autobet.getCurrentModelId());
						if(CollectionUtils.isNotEmpty(cmodeldetail)){//设置自动投注的当前模式为赢后模式
							autobet.setCurrentModelId(currentautobetDetail.get(0).getWinningUseModelId());
						}else{//设置自动投注的当前模式为输后模式
							autobet.setCurrentModelId(currentautobetDetail.get(0).getLoseUseModelId());
						}
						context.getAutoBetRepository().save(autobet);
					}
				}
			}
		}
		
		//设定对号投注
		//正在运行 开始期数小于等于这一期
		List<PermutationBet> pbets = context.getPermutationBetRepository().find_by_game_period_running(gameId, period, RunningState.RUNNING);
		
		if(CollectionUtils.isNotEmpty(pbets)){
			for(PermutationBet pbet:pbets){
				User user = context.getUserRepository().findOne(pbet.getUserId());
				if(user.getCoin()>=pbet.getSuperLimit() || user.getCoin()<=pbet.getLowerLimit()||(period-pbet.getStartBetPeriod())>= pbet.getExecutePeriod()){
					pbet.setState(RunningState.STOPED);
					context.getPermutationBetRepository().save(pbet);
					context.getInternalMessageRepository().save(new InternalMessage(10000, user.getId(),
							"您在"+game.getName()+"游戏设置的名称为"+pbet.getName()+"的对号投注已自动停止,原因可能是下面三种:\n1、已到达设置的金币上限.\n2、已达到设置的金币下限.\n3、已运行完设定的运行期数.\n该邮件为系统自动发送,请勿回复!!"));
				}
			}
		}
		
		
	}
	
	/**
	 * 自动投注下一期  
	 * 自动投注根据当前模式来投注  需判断是否从头开始 来判断使用当前模式还是开始模式
	 * 对号投注根据上一期结果来投注 若上期没有结果,则使用第一个
	 * @param context
	 * @param gameId
	 * @param period
	 * @param i
	 */
	@Transactional
	public void auto_bet(Context context, long gameId, long period, int i) {
		List<AutoBet> autobets = context.getAutoBetRepository().find_by_all_running(gameId, period, RunningState.RUNNING);
		Game game = context.getGameRepository().findOne(gameId);
		long totalMoney = 0;
		int betusers = 0;
		if(CollectionUtils.isNotEmpty(autobets)){
			for(AutoBet autobet:autobets){
				if(CollectionUtils.isEmpty(context.getCathecticRepository().find_all_by_gameId_period_userId(period, gameId, autobet.getUserId(),MunualOrAuto.MUNUAL))){//检测是否已经投注  游戏ID 游戏期数  玩家
					List<CathecticModelDetail> details = null;
					User user = context.getUserRepository().findOne(autobet.getUserId());
					if(autobet.getFromHead()){//使用开始模式
						details = context.
								getCathecticModelDetailRepository().find_by_modelId(autobet.getStartModelId());
						autobet.setFromHead(false);
					}else{//使用当前模式
						details = context.
								getCathecticModelDetailRepository().find_by_modelId(autobet.getCurrentModelId());
					}
					if(CollectionUtils.isNotEmpty(details)){
						CathecticModel model = context.getCathecticModelRepository().findOne(details.get(0).getModelId());
						if((user.getCoin()-model.getTotalBetMoney())<0){
							//这一次自动投注之后账户金币小于设置的金币或者金币不足 自动停止 并发送站内信通知
							autobet.setState(RunningState.STOPED);
							context.getAutoBetRepository().save(autobet);
							context.getInternalMessageRepository().save(new InternalMessage(10000, user.getId(),
									"您在"+game.getName()+"游戏设置的名称为"+autobet.getName()+"的自动投注已自动停止,原因可能是当前账户金币不足.\n该邮件为系统自动发送,请勿回复!!"));
						}else{
							long userCoin = user.getCoin();
							for(CathecticModelDetail detail :details){
								context.getCathecticRepository().save(new Cathectic(
										gameId, period, user.getId(), detail.getBetNumber(),detail.getBetMoney(),
										AutoBetType.AUTO_BET, autobet.getId()));
								context.getDetailsOfFundsRepository().save(new DetailsOfFunds(
										user.getId(), DetailType.AUTO_BET, userCoin,userCoin-detail.getBetMoney(),
										-detail.getBetMoney(), game.getName(),period,detail.getBetNumber()));
								userCoin = userCoin-detail.getBetMoney();
							}
							//更新用户金币
							context.getUserRepository().update_user_coin_for_bet_money(user.getId(),model.getTotalBetMoney());
							totalMoney += model.getTotalBetMoney();
							betusers += 1;
						}
					}
				}
			}
		}
		
		List<PermutationBet> pbets = context.getPermutationBetRepository().find_by_game_period_running(gameId, period, RunningState.RUNNING);
		if(CollectionUtils.isNotEmpty(pbets)){
			for(PermutationBet pbet:pbets){
				if(CollectionUtils.isEmpty(context.getCathecticRepository().find_all_by_gameId_period_userId(period, gameId,pbet.getUserId(),MunualOrAuto.MUNUAL))){//检测是否已经投注  游戏ID 游戏期数  玩家
					List<PermutationBetDetail> pdetails = null;
					if(i == -1){//前面无开奖
						pdetails = context.getPermutationBetDetailRepository().find_by_permutation(pbet.getId());
					}else{
						pdetails = context.getPermutationBetDetailRepository().find_by_permutation_betnumber(pbet.getId(), i);	
					}
					if(CollectionUtils.isNotEmpty(pdetails)){
						User user = context.getUserRepository().findOne(pbet.getUserId());
						CathecticModel model = context.getCathecticModelRepository().findOne(pdetails.get(0).getCathecticModelId());
						if((user.getCoin()-model.getTotalBetMoney()) < 0){
							pbet.setState(RunningState.RUNNING);
							context.getPermutationBetRepository().save(pbet);
							context.getInternalMessageRepository().save(new InternalMessage(10000, user.getId(),
									"您在"+game.getName()+"游戏设置的名称为"+pbet.getName()+"的对号投注已自动停止,原因可能是当前账户金币不足.\n该邮件为系统自动发送,请勿回复!!"));
						}else{
							List<CathecticModelDetail> mdetails = context.getCathecticModelDetailRepository().
									find_by_modelId(model.getId());
							if(CollectionUtils.isNotEmpty(mdetails)){
								long userCoin = user.getCoin();
								for(CathecticModelDetail detail : mdetails){
									context.getCathecticRepository().save(new Cathectic(
											gameId, period, user.getId(), detail.getBetNumber(),detail.getBetMoney(),
											AutoBetType.PERMUTATION_BET, pbet.getId()));
									context.getDetailsOfFundsRepository().save(new DetailsOfFunds(
											user.getId(), DetailType.AUTO_BET, userCoin,userCoin-detail.getBetMoney(),
											-detail.getBetMoney(), game.getName(),period,detail.getBetNumber()));
									userCoin = userCoin-detail.getBetMoney();
								}
								context.getUserRepository().update_user_coin_for_bet_money(user.getId(),model.getTotalBetMoney());
								totalMoney += model.getTotalBetMoney();
								betusers += 1;
							}
						}
					}
				}
			}
		}
		//更新奖池总数和投注人数
		if (totalMoney >0) {
			context.getGamePeriodRepository().update_total_money(gameId, period, totalMoney);
		}
		if (betusers >0) {
			context.getGamePeriodRepository().update_bet_user_numbers(gameId, period, betusers);
		}
	}
	
	/**
	 * 如果总投注为0 那么将不会执行这里的程序   因为不是人工指定结果的时候，开奖的方式为auto_random 如果是这个开奖方式那么是不会进行发放奖励和设置自动投注的
	 * 如果总投注不为0，当人工指定了一个没有人投注的号码时，那么这个号码的赔率将会是无线大。不过这种情况一般应该是不会出现。只要保证始终有网站内部账号
	 * 全包就可以避免
	 * @param context
	 * @param gameId
	 * @param period
	 * @param result
	 * @return
	 */
	private double getOdds(Context context, long gameId, long period, int result){
		//返回金币 = (该期总投注/该期中奖号码总投注)*((10000-抽水比例)/10000)*玩家投注该号码金币数  保留四位小数
		double total = context.getCathecticRepository().sum_by_game_and_period(period, gameId) == null?0:
			Double.parseDouble(context.getCathecticRepository().sum_by_game_and_period(period, gameId).toString());
		double total_winning_number = context.getCathecticRepository().sum_by_game_and_period_and_betnumber(period, gameId, result)==null?0:
			Double.parseDouble(context.getCathecticRepository().sum_by_game_and_period_and_betnumber(period, gameId, result).toString());
		double water_sacal = context.getGameRepository().findOne(gameId).getWaterScale();
		double _odds = (total/total_winning_number)*((10000-water_sacal)/10000);
		return Double.parseDouble(new DecimalFormat("0.####").format(_odds));
	}

	private String generateResultFromNumber(Game game, int resultNumber) {
		
		StringBuffer buffer = null;
		if (game.getId() ==4) {//欢乐10生成方案
			List<Integer> list = new ArrayList<Integer>();
			for (Integer integer : game.getFromNumberList()) {
				list.add(integer);
			}
			list.remove((Object)resultNumber);
			Collections.shuffle(list);
			list.add(0,resultNumber);
			buffer = new StringBuffer();
			for (Integer integer : list) {
				buffer.append(integer+",");
			}
			return buffer.deleteCharAt(buffer.length()-1).toString();
		}else {
			Random r = new Random();
			Collections.sort(game.getFromNumberList());
			int max = game.getFromNumberList().size();
			while (true) {
				int num = 0;
				buffer = new StringBuffer();
				for (int i = 0; i < game.getRepeatTimes(); i++) {
					int t = game.getFromNumberList().get(r.nextInt(max));
					num += t;
					buffer.append(t+",");
				}
				if (num == resultNumber) {
					buffer.deleteCharAt(buffer.length() - 1);
					break;
				}
			}
		}
		return buffer.toString();
	}

	private Date nextLotteryTime(Date current, Game game) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(current);
		calendar.add(Calendar.SECOND, game.getFrequency());
		return calendar.getTime();
	}

	private Date getCurrentDate() {
		Calendar c = Calendar.getInstance();
		c.add(Calendar.SECOND, 2);
		return c.getTime();
	}

}
